#pragma once
#include <map>
#include <vector>
#include <assert.h>
#include "Utils/Logger/Logger.h"

namespace RayFrame
{
  template<typename T> class Pool;
  template<typename T> struct PoolItem;

  template<typename T>
  class Pool
  {
  public:
    typedef T ObjectType;
    struct _iterator;

    struct PoolItem
    {
    private:
      friend class Pool<ObjectType>;
      friend struct _iterator;

      Pool<ObjectType>  *m_parentPool;
      int m_nextIndex, m_prevIndex;
      int m_index;

      PoolItem()
      {
        m_nextIndex = m_prevIndex = -1;
        m_parentPool = NULL;
      }

      T* fetchData(int index)
      {
        if(index >= 0)
          return &m_parentPool->m_data[index]; 
        else
          return NULL;
      };

      PoolItem* fetchItem(int index)
      {
        if(index >= 0)
          return &m_parentPool->m_items[index]; 
        else
          return NULL;
      }
    public:
      T* GetData(){ return fetchData(m_index); };
      T& GetDataRef(){ return *fetchData(m_index); };

      bool operator == (PoolItem& rhs)
      {
        if(m_parentPool == NULL && rhs.m_parentPool == NULL)
          return true;

        return (m_parentPool == rhs.m_parentPool &&
          m_index == rhs.m_index);
      }

      bool operator != (PoolItem& rhs)
      {
        return !(*this == rhs);
      }
    };

    struct _iterator
    {
      friend class Pool;
    private:
      PoolItem* m_item;
    public:
      _iterator(PoolItem* item):m_item(item){ };

      T* GetData(){ return m_item->fetchData(m_item->m_index); };
      T& GetDataRef(){ return *m_item->fetchData(m_item->m_index); };

      bool operator == (const _iterator& rhs)
      {
        if(m_item == NULL && rhs.m_item == NULL)
          return true;
        if(m_item == NULL || rhs.m_item == NULL)
          return false;

        return (m_item->m_parentPool == rhs.m_item->m_parentPool &&
          m_item->m_index == rhs.m_item->m_index);
      }

      _iterator& operator = (const _iterator& rhs)
      {
        m_item = rhs.m_item;
        return *this;
      }

      bool operator != (const _iterator& rhs)
      {
        return !(*this == rhs);
      }

      _iterator& operator ++()
      {
        *this = Next();
        return *this;
      }

      const _iterator operator ++(int a)
      {
        _iterator item = *this;
        ++*this;
        return item;
      }

      T& operator *()
      {
        return *m_item->fetchData(m_item->m_index);
      }

      T* operator->()
      {
        return m_item->fetchData(m_item->m_index);
      }

      _iterator Next()
      {
        PoolItem *item = m_item->fetchItem(m_item->m_nextIndex);
        _iterator iter(item);
        return iter;
      }

      _iterator Prev()
      {
        PoolItem *item = m_item->fetchItem(m_item->m_prevIndex);
        if(item)
        {
          _iterator iter(*item);
          return iter;
        }
        else
          return m_item->m_parentPool->End();
      }
    };
    typedef _iterator iterator;

  protected:
    T           *m_data;
    PoolItem    *m_items;
    int         m_freeHead, m_usedHead;
    //PoolItem    *m_freeHead, *m_usedHead;
    int         m_capability, m_freeCount, m_usedCount;
    const int   DEFAULT_SIZE;

    PoolItem fetchItem(int index)
    {
      if(index >= 0)
        return m_items[index]; 
      else
        return End();
    };
  public:
    /* This function can be used to create or resize the pool.
    However, after resize all pointer to the objects of the pool
    will be invalid. Resize with caution. */
    void Init(int size)
    {
      if(m_capability == 0)
      {
        // create new pool
        if(size < 0)
        {
          Logger::InstanceRef().Warning("Invalid size for ObjectPool.");
          return;
        }

        m_data = new T[size];
        m_items = new PoolItem[size];
        for(int i=0; i<size; i++)
        {
          m_items[i].m_parentPool = this;
          m_items[i].m_nextIndex = m_freeHead;
          if(m_freeHead >= 0)
            m_items[m_freeHead].m_prevIndex = i;
          m_items[i].m_index = i;
          m_freeHead = i;
        }
        m_capability = size;
        m_freeCount = size;
        m_usedCount = 0;

        return;
      }
      
      if(m_capability >= size)
      {
        // pool is already bigger than size
        return;
      }

      if(size <= 0 )
      {
        if(m_capability <= 0)
          size = DEFAULT_SIZE;
        else
          size = m_capability * 2;
      }

      // resize the pool
      PoolItem* newData = new PoolItem[size];

      //// copy old items
      //for(int i=0; i<m_capability; i++)
      //{
      //  newData[i] = m_items[i];
      //}

      // init new items
      for(int i=m_capability; i<size; i++)
      {
        newData[i].m_parentPool = this;

        newData[i].m_nextIndex = m_freeHead;
        newData[m_freeHead].m_prevIndex = i;
        m_freeHead = i;
      }

      SAFE_DELETE_ARRAY(m_items);
      m_items = newData;

      //m_freeCount += size - m_capability;
      m_freeCount = size;
      m_usedCount = 0;
    }
  public:
    Pool(int capability): DEFAULT_SIZE(64)
    {
      m_freeHead = m_usedHead = m_usedHead = NULL;
      m_data = NULL;
      m_capability = 0;

      Init(capability);
    };

    Pool(): DEFAULT_SIZE(64)
    {
      m_freeHead = m_usedHead = -1;
      m_items = NULL;
      m_data = NULL;
      m_capability = m_freeCount = m_usedCount = 0;
    }

    ~Pool()
    {
      //while(item)
      //{
      //  if(!item->GetData())
      //    continue;
      //  item->GetData()->internalDestroy();
      //  if(item->m_nextIndex == -1)
      //    break;
      //  item = &m_items[item->m_nextIndex];
      //}
      SAFE_DELETE_ARRAY(m_items);
      SAFE_DELETE_ARRAY(m_data);
    }

    int GetFreeCount(){ return m_freeCount; };
    int GetUsedCount(){ return m_usedCount; };
    int GetCapability(){ return m_capability; };
    bool Exahusted(){ return m_freeCount == 0; };

    iterator FreeBegin()
    {
      if(m_freeHead >= 0)
        return iterator(&m_items[m_freeHead]);
      else
        return End();
    }

    iterator UsedBegin()
    {
      if(m_usedHead >= 0)
        return iterator(&m_items[m_usedHead]);
      else
        return End();
    }

    iterator End()
    {
      iterator iter(NULL);
      return iter;
    }

    iterator Pop()
    {
      if(m_freeHead< 0)
      {
        assert(Exahusted());
        return End();  // Objects are used up
      }

      int tempIndex = m_freeHead;
      m_freeHead = m_items[m_freeHead].m_nextIndex;
      if(m_freeHead >= 0)
        m_items[m_freeHead].m_prevIndex = -1;

      if(m_usedHead >= 0)
        m_items[m_usedHead].m_prevIndex = tempIndex;
      m_items[tempIndex].m_nextIndex = m_usedHead;
      m_items[tempIndex].m_prevIndex = -1;
      m_usedHead = tempIndex;
    
      m_items[m_usedHead].GetData()->internalCreate();

      m_freeCount--;
      m_usedCount++;
      return iterator(&m_items[m_usedHead]);
    }

    void Push(iterator& iter)
    {
      iter.GetData()->internalDestroy();
      
      if(iter.m_item->m_index == m_usedHead)
        m_usedHead = iter.m_item->m_nextIndex;

      if(iter.m_item->m_prevIndex >= 0)
        m_items[iter.m_item->m_prevIndex].m_nextIndex = iter.m_item->m_nextIndex;
      if(iter.m_item->m_nextIndex >= 0)
        m_items[iter.m_item->m_nextIndex].m_prevIndex = iter.m_item->m_prevIndex;

      iter.m_item->m_nextIndex = m_freeHead;
      if(m_freeHead >= 0)
        m_items[m_freeHead].m_prevIndex = iter.m_item->m_nextIndex;

      m_freeHead = iter.m_item->m_index;

      m_freeCount++;
      m_usedCount--;
    }
  };
};
